package core;
/**
 * This class is used to update programs, using a URI and regex.
 * @author Gianni Gambetti
 * @since April 5, 2009
 */


import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
 * TODO:
 * add update extractor, so the updates can be extracted, and used without restart
 * add an dependancy check, to make sure we can use it, and resolve dependancies if we can't
 */

public class UpdateEngine {

	public static void main(String[] args){
		
		//UpdateEngine u = new UpdateEngine("","","","",0,true);
		//u.debug = true;
		//u.moreRecent("/Phrox_v0.3.140.zip", "/");
		//String temp ="/Phrox_v0.3.140.zip";
		//temp = temp.substring(temp.lastIndexOf(".")+1);
		//System.out.println(temp);
		/*
		char[] a = new String(System.getProperty("line.separator")).toCharArray();
		for(char b : a){
			System.out.println((int)b);
		}
		/
		try {
			String in ="http://code.google.com/p/phroxchat";
			//String in ="http://code.google.com/p/phorxchat/downloads/list?can=1&q=&colspec=Filename+Summary+Uploaded+Size+DownloadCount";//all phorx downloads
			String reg ="http.{0,50}?Phrox_v[\\d\\.]*.zip";//WORKS FOR PHROX CHAT
			String fil = "";
			UpdateEngine u= new UpdateEngine(in,reg,"Phrox_v0.2.131.zip",reg,10,false);
			u.debug =true;
			System.out.println(u.checkForUpdate());
			u.update();
			//System.out.println(u.update());

		} catch (Exception e) {
			e.printStackTrace();
		}//*/
	}
	
	public static String fD = "/";
	
	
	private URL remoteLocation = null;//URL location to check, for remote
	private String remoteRegex = null;//remote regex to match to (assuming the URL isn't a file
	private String curentVer = null;//current file + version
	private String localRegex = null;//regex local (assuming we don't use a "update" folder)
	private int minLen = 0;
	
	private boolean useUpdateFolder = false;
	private String updateFile = null;

	private UpdateType update = UpdateType.notChecked;//type of update
	/*
	notChecked	= updater hasn't checked for an update
	none		= there is no update
	local		= the most recent update has already been downloaded
	remote		= the most recent still needs to be downloaded

	 */
	private enum UpdateType {notChecked, none, local, remote};
	
	//public boolean debug = false;


	/**
	 * 
	 * @param httpLoc The URL of the Update database, or the Update file location
	 * <br>This allows the program to look at a bunch of updates for the most recent, or just get the most recent.
	 * <br>for small programs redownloading the whole thing is not a problem, but for larger ones it could be.
	 * @param remReg the remote regex that file names must fit to be qualified as updates. ex: 'http.{0,50}?Phrox_v[\\d\\.]*.zip' this contains the url that will be included.
	 * @param curVer the current version. ex: 'Phrox_v0.012.zip'
	 * @param locReg the local regex that files must fit to be qualified as updates (usually the last bit of the remReg)
	 * @param updateFolder if the program uses a folder called 'update' to store it's downloaded updates
	 * @param minlen the minimum length an update path can have. It should be: 'constans' + # +'.'+'extension'.
	 * <br> ex: 'Phrox_v' + # + '.' + 'zip' (11) 'Phrox_v.zip'
	 */
	public UpdateEngine(String httpLoc, String remReg, String curVer, String locReg, int minlen, boolean updateFolder){
		try {
			remoteLocation = new URL(format(httpLoc));
		} catch (MalformedURLException e) {
			remoteLocation = null;
		}
		remoteRegex = remReg;
		curentVer = fD+format(curVer);
		localRegex = locReg;
		minLen = minlen;
		useUpdateFolder = updateFolder;
	}

	public boolean hasUpdate() throws UpdateException{
		if(update == UpdateType.notChecked){
			//havn't checked yet. :(
			throw new UpdateException();
		}
		return !update.equals(UpdateType.none);
	}

	public boolean checkForUpdate(){
		if(update != UpdateType.notChecked){
			return update != UpdateType.none;
		}
		boolean ret = false;
		String loc = checkLocal();
		String rem = checkRemote();
		if(loc.length() < minLen && rem.length() < minLen){
			update = UpdateType.none;

		}else{
			updateFile = moreRecent(loc,rem);
			updateFile = moreRecent(updateFile, curentVer);
			
			ret = true;
			update = UpdateType.remote;
			
			if(updateFile.equals(curentVer)){
				update = UpdateType.none;
				ret = false;
				//no update
			}else if(updateFile.equals(loc)){
				//local update
				update = UpdateType.local;
			}//else remote update
		}
		return ret;
	}

	private String checkLocal(){
		String ret = fD;
		//check local first
		try{
			File f = new File("");//this folder
			if(useUpdateFolder){
				f = new File(f.getAbsolutePath()+File.separatorChar+"update"+File.separatorChar);
			}else{
				f = new File(f.getAbsolutePath()+File.separatorChar);
			}
			
			if(network.NetworkVars.debug){
				System.out.println("Local Update:"+f.getAbsolutePath());
			}
			
			File[] fList = f.listFiles();//get folder directory...

			//System.out.println(f.getAbsolutePath());
			//System.out.println(f.isDirectory());

			Pattern regPat = Pattern.compile(localRegex);
			ArrayList<String> matches = new ArrayList<String>();

			if (fList != null) {
				for (File subFile : fList) {
					//System.out.println("FILE:"+subFile.getName());
					if(subFile.isFile()){
						Matcher regMat = regPat.matcher(subFile.getName());
						if (regMat.find()) {
							//the file name matches
							matches.add(subFile.getAbsolutePath());
						}
					}//don't check sub folders, it will be saved here (by program) 
					//or user will put it here ( shame on user who doesn't)
				}

				if(matches.isEmpty()){
					//no local matches
					return fD;
				}

				sort(matches);
				ret = (matches.get(matches.size()-1));//most recent local update

			}
		} catch(Exception e){
			return fD;
		}
		return ret;
	}

	private String checkRemote(){
		String ret = fD;
		if(remoteLocation == null){
			return fD;
		}
		String temp = format(remoteLocation.getFile());
		temp = temp.substring(temp.lastIndexOf(fD)+1);
		if(temp.indexOf(".") > 0){
			//this is a file. could be .html :(
			
			//assuming files are the update
			temp = temp.substring(temp.lastIndexOf(".")+1);
			if(isArchive(temp)){
				//if it's a valid archive file, we can download.
			}
			ret = moreRecent(temp, curentVer);

		}else{
			//this is a directory, or a html page
			//check the page for links to update files

			try{
				String source = "";
				//create a url connection
				URLConnection url_Connection = remoteLocation.openConnection();
				url_Connection.setReadTimeout(5000);

				//bufferedreaded is much faster, because it has readLine()
				BufferedReader reader = new BufferedReader(new InputStreamReader(url_Connection.getInputStream()));



				while(true){
					String a = reader.readLine();
					if (a == null){
						break;
					}
					source = source + a;
				}


				//regex searching
				if(network.NetworkVars.debug){
					System.out.println(source);
				}

				//set up regex
				Pattern regPat = Pattern.compile(remoteRegex);
				Matcher regMat = regPat.matcher(source);

				ArrayList<String> matches = new ArrayList<String>();

				//get the matches to the regex.
				while (regMat.find()) {
					matches.add(regMat.group());
				}
				if(matches.isEmpty()){
					return fD;//no matches to regex.
				}

				sort(matches);//sort the list so that the newest ends up at the end

				ret = matches.get(matches.size()-1);//store download url

				reader.close();
			}catch(Exception e){
				e.printStackTrace();
				return fD;
			}
		}
		return ret;

	}

	/**
	 * This method will check for and download updates
	 * @return returns true if it found an update, and downloaded it without any problems.
	 */
	public boolean update(){
		boolean ret = false;
		try {
			if(hasUpdate()){
				ret =true;
			}
		} catch (UpdateException e) {
			if(checkForUpdate()){
				ret = true;
			}
		}
		if(ret){
			ret = downloadUpdate();
		}
		return ret;
	}

	/**
	 * @param str1 file path
	 * @param str2 second file path
	 * @return Returns str1 or str2, depending on which is the more recent version of the software
	 */
	public String moreRecent(String str1, String str2){
		//check file names, they should contain version info
		String ret = ( str1.substring(str1.lastIndexOf(fD),str1.length()).compareTo(str2.substring(str2.lastIndexOf(fD),str2.length()))  > 0? str1 : str2 );
		if(network.NetworkVars.debug){
			System.out.println(str1+"\t\t"+str2+"\t:\t"+ret);
		}
		return ret;

	}

	public boolean downloadUpdate(){
		if(update == UpdateType.local){
			//local
			return true;
		}


		try{

			//create new url connection, and buffered reader.
			URLConnection url_Con = new URL(updateFile).openConnection();
			url_Con.setReadTimeout(5000);
			InputStreamReader reader = (new InputStreamReader(url_Con.getInputStream()));



			//set up file name.
			ByteIO.createOutputStream(updateFile.substring(updateFile.lastIndexOf(fD)+1));
			//IO.createOutputFile(d_File.substring(d_File.lastIndexOf(fD)+1));
			System.out.println("Downloading Update");

			//read

			//is java changing the line feeds to line feeds + carage returns?
			int i = 1;
			while(i > 0){
				//use a nice size for buffer reading
				char[] buf = new char[4 * 1024];
				i = reader.read(buf);
				String in="";
				for(int j=0; j<i; j++){
					in += buf[j];
				}
				//save right away, so we don't store 'large' files in memory.
				ByteIO.write(in.getBytes());

				//System.out.print(in);


			}
			ByteIO.closeOutputStream();
			System.out.println("Downloading done");
		} catch(Exception e){
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * @param str the file extension to check
	 * @return returns true, if this file extension is a known archive type
	 */
	private static boolean isArchive(String str){
		String arch = "tar-gz-7z-zip-jar-tgz-bz2-tbz2-lzma-tlz";
		if(arch.contains(str)){
			return true;
		}
		return false;
	}
	
	public String getFile(){
		return updateFile;
	}

	/**
	 * This sorts the list of version URLs.
	 * @param list
	 */
	private static void sort(ArrayList<String> list){
		int i, j;
		String currentElement; 
		// starting at the second element in the array, sequentially 
		// place each element in the array in its correct location

		for (i = 1; i < list.size(); i++)
		{ 
			j = i;

			currentElement = list.get(j);


			while ( ( j > 0) && (list.get(j-1).compareTo(currentElement)) > 0) 
			{
				//list.add(j, list.get(j-1)); 
				list.set(j, list.get(j-1));
				j --; 
			}
			list.set(j, currentElement); 
		}

	} // end insertionSort method

	private static String format(String str){
		return str.replace("\\",fD).replace("/", fD);
	}


}

class UpdateException extends Exception {

	public UpdateException() {
		super();
	}

	public UpdateException(String arg0, Throwable arg1) {
		super(arg0, arg1);
	}

	public UpdateException(String arg0) {
		super(arg0);
	}

	public UpdateException(Throwable arg0) {
		super(arg0);
	}

}


